package com.simpou.commons.utils.tests;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;

import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;

import java.util.PriorityQueue;
import java.util.Queue;


/**
 * Executa JUnit tests. Somente executa os métodos se todos métodos acessíveis
 * na classe estão definidos na classe de teste.
 *
 * @see Repeat Executar testes em loop.
 * @see ClassTest Definir classe que está sendo testada pela classe de teste.
 * @see IgnoreValidation Ignorar todas as validações.
 * @author Jonas Pereira
 * @since 2011-07-13
 * @version 2012-05-15
 */

//TODO documentar melhor
public final class TestRunner extends BlockJUnit4ClassRunner {
    /**
     * Logger.
     */
    private final Log logger = LogFactory.getLog(this.getClass());

    /**
     * Serviço que executa de fato todas ações.
     */
    private final TestRunnerService service;

    /**
     * Define os métodos que serão executados e sua ordem de execução. Somente
     * para os anotados com "TestOrder".
     */
    private Queue<TestExecutionQueueItem> orderExecList = new PriorityQueue<TestExecutionQueueItem>();

    /**
     * Para realização de testes.
     *
     * @param testClass Classe de teste. Aquele que testa outra classe.
     * @param service Para injeção do serviço.
     * @throws InitializationError Erro de validação.
     */
    TestRunner(final Class<?> testClass, final TestRunnerService service)
        throws InitializationError {
        super(testClass);
        this.service = service;
        checkExecution();
    }

    /**
     * Define o número de repetições de classe e realiza validações. Somente
     * executa os métodos se todos métodos acessíveis na classe estão definidos
     * na classe de teste. Para ignorar validações use anotação
     * IgnoreValidation. Classes internas e seus métodos não são validados.
     *
     * @param testClass Classe de teste. Aquele que testa outra classe.
     * @throws org.junit.runners.model.InitializationError Erro de validação.
     */
    public TestRunner(final Class<?> testClass) throws InitializationError {
        super(testClass);
        service = new TestRunnerService(testClass, logger);
        checkExecution();
    }

    /**
     * @throws InitializationError Erro de checagem.
     */
    private void checkExecution() throws InitializationError {
        // checa consistência de classe de teste
        service.checkClass();

        // checa métodos a serem testados
        service.checkMethods();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Description describeChild(final FrameworkMethod method) {
        String methodName = testName(method);
        Description description = service.getMethodDescription(methodName);

        return description;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void runChild(final FrameworkMethod method,
        final RunNotifier notifier) {
        String methodName = method.getName();
        Integer order = service.getMethodOrderByName(methodName);

        if (order != null) {
            // enfileira a execução do método ordenado
            orderExecList.offer(new TestExecutionQueueItem(method, notifier,
                    order));
        } else {
            // executa os métodos não ordenados
            runChild(method, notifier, null);
        }

        if (!orderExecList.isEmpty() && !service.hasOrder()) {
            // executa os métodos ordenados
            TestExecutionQueueItem testExecutionQueueItem;

            while (!orderExecList.isEmpty()) {
                testExecutionQueueItem = orderExecList.poll();
                runChild(testExecutionQueueItem.getMethod(),
                    testExecutionQueueItem.getNotifier(),
                    testExecutionQueueItem.getTestOrder());
            }
        }
    }

    /**
     * Executa de fato os métodos de teste.
     *
     * @param method Método.
     * @param notifier Notificador JUnit.
     * @param order Ordem de execução. Null caso não seja ordenado.
     */
    private void runChild(final FrameworkMethod method,
        final RunNotifier notifier, final Integer order) {
        String methodName = method.getName();
        Integer times = service.getMethodRepeatsByName(methodName);

        service.sinalizeMethodExecution(methodName);

        // executa método em loop
        String logMsg;

        for (int i = 0; i < times; i++) {
            logMsg = service.getLogMethodExecution(methodName, times, i + 1,
                    order);
            System.out.println(logMsg);
            super.runChild(method, notifier);
        }
    }
}
